REDUCE + SWITCH + COND [ABAP740]

Heute im Code-Dojo hatte ich die Aufgabe gestellt, eine Funktion zu schreiben, die einen String mit variabel zu bestimmender Länge und zufälligen Zeichenfolgen aus Zahlen und Buchstaben zurück liefert. Zum Beispiel “I71B7HJ4BG” oder “6EE17ICBF54IE486EHD8”.

Idee

Mit VALUE und FOR sollte ein String Zeichen für Zeichen zusammengesetzt werden. Mit einer Zufallsfunktion sollte ermittelt werden, ob ein Buchstabe oder eine Zahl eingesetzt werden soll. Per SWITCH sollte ebenfalls eine Zufallsfunktion aufgerufen werden, die eine Zahl bzw. einen Buchstaben zurück liefert.. Per String-Konkatenation sollten die zufälligen Zeichen zusammengesetzt werden.

Abweichung

die Aufgabe lässt sich mit VALUE nicht lösen. Stattdessen muss REDUCE genommen werden.

Code

Für jede Stelle des zu generierenden Strings (FOR – UNTIL – NEXT) wird eine Funktion RND_TYPE aufgerufen. Diese gibt zufällig den Wert TRUE oder FALSE zurück. Per SWITCH-Anweisung wird entschieden, ob eine Zahl (FALSE) oder ein Buchstabe (TRUE) generiert werden soll. Das generierte Zeichen wird per String-Konkatenation Zeichen für Zeichen zusammengebaut.

 

REPORT.

CLASS main DEFINITION.
   PUBLIC SECTION.
     DATA rnd_num TYPE REF TO cl_abap_random_int.
     DATA rnd_chr TYPE REF TO cl_abap_random_int.
     METHODS constructor.
     METHODS rnd_type
       RETURNING VALUE(type) TYPE boolean.
     METHODS create_random_string
       IMPORTING max           TYPE i
       RETURNING VALUE(string) TYPE string.
     METHODS get_random_char
       RETURNING VALUE(char) TYPE char01.
     METHODS get_random_number
       RETURNING VALUE(number) TYPE numc01.
 ENDCLASS.

CLASS main IMPLEMENTATION.
   METHOD constructor.
     rnd_chr = cl_abap_random_int=>create( seed = CONV #( sy-uzeit ) min = 0 max = 25 ).
     rnd_num = cl_abap_random_int=>create( seed = CONV #( sy-uzeit ) min = 0 max = 9 ).
   ENDMETHOD.
   METHOD rnd_type.

    type = COND #( LET random = get_random_number( ) IN
                    WHEN random <= 5 THEN abap_true
                    ELSE abap_false ).
   ENDMETHOD.

  METHOD get_random_char.
     DATA(offset) = rnd_num->get_next( ).
     char = sy-abcde+offset(1).
   ENDMETHOD.

  METHOD get_random_number.
     number = rnd_num->get_next( ).
   ENDMETHOD.

  METHOD create_random_string.

    string = REDUCE #( INIT text = ``
                        FOR i = 1
                        UNTIL i > max
                        NEXT text = text && SWITCH #( rnd_type( )
                                              WHEN abap_true  THEN get_random_char( )
                                              WHEN abap_false THEN get_random_number( ) ) ).

  ENDMETHOD.

ENDCLASS.

PARAMETERS p_len type i DEFAULT 10.
PARAMETERS p_str TYPE char20 MODIF ID a.

AT SELECTION-SCREEN OUTPUT.
   LOOP AT SCREEN.
     CASE screen-group1.
       WHEN 'A'.
         screen-input = '0'.
         MODIFY SCREEN.
     ENDCASE.
   ENDLOOP.

AT SELECTION-SCREEN.
   p_str = NEW main( )->create_random_string( p_len ).

Lessons Learned

Mit REDUCE können Operationen auf einen Datentyp “reduziert” werden. Mit VALUE funktioniert das nicht.

string = REDUCE #( INIT text = ``
                   FOR i = 1
                   UNTIL i > max
                   NEXT text = text && SWITCH #( rnd_type( )
                                         WHEN abap_true  THEN get_random_char( )
                                         WHEN abap_false THEN get_random_number( ) ) ).

Mit SWITCH können nur Exakte Werte abgefragt werden (wie bei CASE auch mit OR verknüpft). Es sind jedoch keine “Größer-/ Kleiner-Vergleiche” möglich.

[...] SWITCH #( rnd_type( )
        WHEN abap_true  THEN get_random_char( )
        WHEN abap_false THEN get_random_number( ) ) ).

Mit COND können beliebige Bedingungen geprüft werden. Allerdings muss hier jede Bedingung separat angegeben werden. Wenn der abzufragende Wert das Ergebnis einer Funktion ist, so sollte mit LET gearbeitet werden, um nicht für jede Bedingung die Funktion aufrufen zu müssen.

var = COND #( LET random = get_random_number( ) IN
              WHEN random <= 5 THEN abap_true
              ELSE abap_false ).

Die implizite Typ-Definition mit INIT (bei der REDUCE-Anweisung) ist mit Vorsicht zu genießen! Ich hatte aus Gewohnheit einen leeren “String” mit Hochkomma-Space-Hochkomma definiert. In Wirklichkeit hatte ich damit aber einen CHAR(1)-Feld definiert und die Funktion hat immer nur ein Zeichen zurück geliefert. Die String-Konkatenation hat diesen fest definierten Typ also nicht automatisch erweitert, so wie es beim String der Fall ist. Erst die Verwendung eines echten Strings durch die Backticks “ liefert das gewünschte Ergebnis.

Es kann auch der Typ direkt angegeben werden (INIT text TYPE string) aber dann ist keine Vorbelegung mehr möglich. Eine implizite Definition durch Vorbelegung ist dann jedoch wieder durch die Verwendung von CONV möglich: INIT text = CONV string( ‘hallo’ )

Bei der FOR-Funktion (FOR i = 1) muss das Hochzählen der Variable (THEN i + 1) nicht zwingend definiert werden! Wird THEN nicht angegeben, so wird implizit die Inkrementierung um Eins vorgenommen:

[...] FOR i = 1 UNTIL i > 10 [...]

 

Enno Wulff